package demo.c2;

import org.junit.Test;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * ThreadApiDemo
 *
 * @author chisheng
 */
public class ThreadApiDemo {

    @Test
    public void testSleep() throws InterruptedException {

        /* public static native void sleep(long millis) throws InterruptedException
         * public static void sleep(long millis, int nanos)
         *  Causes the currently executing thread to sleep (temporarily cease execution)
         *  for the specified number of milliseconds,
         *  subject to the precision and accuracy of system timers and schedulers.
         *  The thread does not lose ownership of any monitors.
         */

        new Thread(() -> {
            try {
                final long s = System.currentTimeMillis();
                Thread.sleep(2_000L);
                final long e = System.currentTimeMillis();
                System.out.printf("MyThread total spend %d ms.\n", (e - s));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "MyThread").start();

        final long s = System.currentTimeMillis();
        Thread.sleep(3_000L);
        final long e = System.currentTimeMillis();
        System.out.printf("main thread total spend %d ms.\n", (e - s));

        /* MyThread total spend 2004 ms.
         * main thread total spend 3005 ms.
         *  sleep() 方法指挥使得当前的线程进入睡眠状态，不会影响到其他线程 */
    }

    @Test
    public void testTimeUnit() throws InterruptedException {

        /* A TimeUnit represents time durations at a given unit of granularity and provides utility methods
         * to convert across units, and to perform timing and delay operations in these units.
         * A TimeUnit does not maintain time information, but only helps organize and use time representations
         * that may be maintained separately across various contexts.
         * A nanosecond is defined as one thousandth of a microsecond, a microsecond as one thousandth of a millisecond,
         * a millisecond as one thousandth of a second, a minute as sixty seconds, an hour as sixty minutes,
         * and a day as twenty four hours.
         *
         * TimeUnit.DAYS.sleep(long)
         * TimeUnit.HOURS.sleep(long)
         * TimeUnit.MINUTES.sleep(long)
         * TimeUnit.SENCONDS.sleep(long)
         * TimeUnit.MILLISENCONDS.sleep(long)
         * TimeUnit.MICROSECONDS.sleep(long)
         * TimeUnit.MILLISECONDS.sleep(long) */

        new Thread(() -> {
            try {
                final long s = System.currentTimeMillis();
                TimeUnit.SECONDS.sleep(2);
                final long e = System.currentTimeMillis();
                System.out.printf("MyThread total spend %d ms.\n", (e - s));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "MyThread").start();

        final long s = System.currentTimeMillis();
        TimeUnit.SECONDS.sleep(3);
        final long e = System.currentTimeMillis();
        System.out.printf("main thread total spend %d ms.\n", (e - s));
    }

    @Test
    @SuppressWarnings("all")
    public void testYield() throws InterruptedException {

        /* A hint to the scheduler that the current thread is willing to yield its current use of a processor.
         * The scheduler is free to ignore this hint.
         * Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilise a CPU.
         * Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.
         * It is rarely appropriate to use this method.
         * It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions.
         * It may also be useful when designing concurrency control constructs such as the ones in the java.util.concurrent.locks package. */

        IntStream.range(0, 2).mapToObj(i ->
                new Thread(() -> {
                    // 若 thread-0 执行 yield() 则会对 CPU 发出放弃资源的 hint
                    // if (i == 0) {
                    //      System.out.println(Thread.currentThread().getName() + " is yield CPU.");
                    //      Thread.yield();
                    //  }

                    System.out.println(Thread.currentThread().getName() + " is running.");

                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName());
                }, "thread-" + i)
        ).forEach(Thread::start);

        // Avoid the main calling system.exit(0) too early
        TimeUnit.SECONDS.sleep(3);
    }

    @Test
    @SuppressWarnings("all")
    public void testPriority() throws InterruptedException {

        /* // Changes the priority of this thread.
         * public final void setPriority(int newPriority) {
         *     ...
         *     // public static final int MAX_PRIORITY = 10
         *     // public static final int MIN_PRIORITY = 1
         *     if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
         *         throw new IllegalArgumentException();
         *     }
         *
         *     // 单个最大优先级小于等于线程组的最大优先级
         *     if((g = getThreadGroup()) != null) {
         *         if (newPriority > g.getMaxPriority()) {
         *             newPriority = g.getMaxPriority();
         *         }
         *
         *     setPriority0(priority = newPriority);
         *     }
         * }
         *
         * public final int getPriority()
         *  Returns this thread's priority.
         */

        final ThreadGroup myThreadGroup = new ThreadGroup("MyThreadGroup");
        myThreadGroup.setMaxPriority(7);
        final Thread thread = new Thread(myThreadGroup, () -> {
        }, "MyThread");
        thread.setPriority(10);  // 企图未遂
        System.out.println("MyThread final priority: " + thread.getPriority());

        /*******************************************************************************************/

        AtomicInteger thread01Counter = new AtomicInteger(0);
        AtomicInteger thread02Counter = new AtomicInteger(0);

        final Thread thread01 = new Thread(() -> {
            try {
                while (true) {
                    thread01Counter.incrementAndGet();
                    TimeUnit.MILLISECONDS.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread01");

        final Thread thread02 = new Thread(() -> {
            try {
                while (true) {
                    thread02Counter.incrementAndGet();
                    TimeUnit.MILLISECONDS.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread02");

        thread01.setPriority(1);
        thread02.setPriority(10);
        thread01.start();
        thread02.start();

        // Avoid main thread calling System.exit(0) too early.
        TimeUnit.SECONDS.sleep(5);

        System.out.println("Thread01 count: " + thread01Counter.get());
        System.out.println("Thread02 count: " + thread02Counter.get());
    }

    @Test
    public void testGetId() {

        /* Returns the identifier of this Thread.
         * The thread ID is a positive long number generated when this thread was created.
         * The thread ID is unique and remains unchanged during its lifetime.
         * When a thread is terminated, this thread ID may be reused. */

        final Thread thread = new Thread(() -> {
        }, "MyThread");
        System.out.println("MyThread's id: " + thread.getId());
    }

    @Test
    @SuppressWarnings("all")
    public void testInterrupt() throws InterruptedException {

        /* public void interrupt() {
         *     // 线程处于死亡状态，调用此方法将会被忽略，但是其处于中断状态
         *     if (this != Thread.currentThread()) {
         *         ...
         *         synchronized (blockerLock) {
         *             ...
         *             // 如果一个线程被 interrupt 则该标记将会被设定
         *             // 若被中断线程处于可中断方法的阻塞时，执行该线程的 interrupt() 后该标记将会被清除，以保证其后面的语句正常执行
         *             interrupted = true;
         *             ...
         *         }
         *     }
         *     interrupted = true;
         *     ...
         * }
         *
         * If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int)
         * methods of the Object class, or of the join(), join(long), join(long, int), sleep(long),
         * or sleep(long, int), methods of this class, then its interrupt status will be cleared and
         * it will receive an InterruptedException. */

        final Thread thread = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException ignore) {
                System.out.println("Oh, I am interruptted.");
            }

            System.out.println("But i am alive.");
        });

        thread.start();
        thread.interrupt();
    }

    @Test
    @SuppressWarnings("all")
    public void testIsInterrupted() throws InterruptedException {

        /* // Tests whether this thread has been interrupted.
         * // The interrupted status of the thread is unaffected by this method.
         * public boolean isInterrupted() {
         *     return interrupted;
         * } */

        final Thread thread01 = new Thread(() -> {
            while (true) {
                // do nothing, just empty loop
            }
        }, "MyThread01");

        // 设置 deamon 为 true，否则线程不会结束
        thread01.setDaemon(true);
        thread01.start();
        System.out.println("MyThread01 is interrupted: " + thread01.isInterrupted());
        thread01.interrupt();
        System.out.println("MyThread01 is interrupted: " + thread01.isInterrupted());

        /*******************************************************************************************/

        final Thread thread02 = new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException ignore) {
                    System.out.println("MyThread02 is interrupted: " + Thread.currentThread().isInterrupted());
                }
            }
        }, "MyThread02");

        // 设置 deamon 为 true，否则线程不会结束
        thread02.setDaemon(true);
        thread02.start();
        System.out.println("MyThread02 is interrupted: " + Thread.currentThread().isInterrupted());
        thread02.interrupt();
        // 中断标记被清除
        System.out.println("MyThread02 is interrupted: " + Thread.currentThread().isInterrupted());
    }

    @Test
    public void testInterrupted() throws InterruptedException {

        /* // 若当前 isInterrupted() == true, 则返回 true 并清除中断信号
         * public static boolean interrupted() {
         *     Thread t = currentThread();
         *     boolean interrupted = t.interrupted;
         *
         *     // We may have been interrupted the moment after we read the field,
         *     // so only clear the field if we saw that it was set and will return
         *     // true; otherwise we could lose an interrupt.
         *     if (interrupted) {
         *         // 擦除 interrupt 标识
         *         t.interrupted = false;
         *         // 控制擦除线程 interrupt 标识
         *         clearInterruptEvent();
         *     }
         *
         *     return interrupted;
         * } */

        final Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("MyThread02 is interrupted: " + Thread.interrupted());
            }
        }, "MyThread");

        thread.setDaemon(true);
        thread.start();
        TimeUnit.MILLISECONDS.sleep(100);
        thread.interrupt();
        TimeUnit.MILLISECONDS.sleep(100);
        thread.interrupt();
    }

    @Test
    public void test01() {
        System.out.println(Thread.interrupted());
        System.out.println(Thread.currentThread().isInterrupted());
        Thread.currentThread().interrupt();
        System.out.println(Thread.currentThread().isInterrupted());

        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            System.out.println("I will be interrupted still.");
        }
    }

    @Test
    public void testJoin() throws InterruptedException {

        /* // Waits for this thread to die.
         * public final void join() throws InterruptedException {
         *     join(0);
         * }
         *
         * public final synchronized void join(final long millis)
         *  Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever.
         *  This implementation uses a loop of this.wait calls conditioned on this.isAlive.
         *  As a thread terminates the this.notifyAll method is invoked.
         *  It is recommended that applications not use wait, notify, or notifyAll on Thread instances. */

        final List<Thread> threads = IntStream.range(0, 2).mapToObj(i -> new Thread(() -> {
            for (int j = 0; j < 10; j++) {
                System.out.println(Thread.currentThread().getName() + ": " + j);

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread-" + i)).collect(Collectors.toList());

        threads.forEach(Thread::start);
        threads.forEach((thread) -> {
            try {
                System.out.println(thread.getName() + " is alive: " + thread.isAlive());
                System.out.println(thread.getName() + " is joined by main thread.");
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        for (int j = 0; j < 10; j++) {
            System.out.println(Thread.currentThread().getName() + ": " + j);
            TimeUnit.SECONDS.sleep(1);
        }
    }

}
